/*
 * Created on 02/25/2012 - 3:15 AM
 */
package sembr.vtk.mesh;

import sembr.vtk.data.input.VtkDataReceiver;
import sembr.vtk.visualizing.VtkMultRenderPanel;
import vtk.*;

/**
 *
 * @author Adriano
 */
public class vtkFDTD3DMesher 
{
    private VtkDataReceiver inputData;
    private int[] extent;
    private vtkTriangleFilter triangulator;
    private vtkIdList listOfInsideCells;
    private vtkDoubleArray scalars;
    private vtkCamera camera;
    private vtkPanel panel;
    private VtkMultRenderPanel multRenderPanel;
    private vtkOutlineFilter outlineFilter;
    private vtkActor outlineActor;
    private vtkActor gridActor;
    private vtkActor objectActor;
    private vtkActor pointsActor;
    vtkSphereSource sphere;
    vtkFillHolesFilter fillHolesFilter; 
        
    public vtkFDTD3DMesher(VtkDataReceiver input)
    {
        LoadVTKLibraries();
        inputData = input;
        multRenderPanel = new VtkMultRenderPanel();
        //generateFDTD3DGrid(0.05);
        generateFDTD3DGrid(10.00);
        //generateFDTD3DGrid(3.00);
        setCamera();
        //setPanel();
        setMultRenderPanel();
    }
        
    private void generateFDTD3DGrid(double resolution)
    {       
        double[] bounds = inputData.getPolyData().GetBounds();
        
        System.out.println("Object´s bounds:");   
        System.out.println("x1: " + bounds[0] + "; x2: " + bounds[1]); 
        System.out.println("y1: " + bounds[2] + "; y2: " + bounds[3]); 
        System.out.println("z1: " + bounds[4] + "; z2: " + bounds[5]); 
        
        setFDTD3DGridDomain(bounds);        
        
        vtkPoints centerPoints = getCellCenters(resolution);
        double[] centerPointsBounds = centerPoints.GetBounds();
        
        System.out.println("Center Points´s bounds:");
        System.out.println("x1: " + centerPointsBounds[0] + "; x2: " + centerPointsBounds[1]); 
        System.out.println("y1: " + centerPointsBounds[2] + "; y2: " + centerPointsBounds[3]); 
        System.out.println("z1: " + centerPointsBounds[4] + "; z2: " + centerPointsBounds[5]); 
        
        //vtkPoints centerPointsInside = getCenterPointsInsideObject(centerPoints, sphere);   
        vtkPoints centerPointsInside = getCenterPointsInsideObject(centerPoints); 
        setCellData();
                
        vtkUnstructuredGrid gridObject = discretizeObjectInVoxels(centerPointsInside, resolution);
       
//         //test
//        vtkPolyData testCenterPoints = new vtkPolyData();
//        testCenterPoints.SetPoints(centerPointsInside);
//        vtkVertexGlyphFilter filter = new vtkVertexGlyphFilter(); 
//        filter.SetInput(testCenterPoints);

        //vtkPolyDataMapper fillHolesMapper = new vtkPolyDataMapper();
        //fillHolesMapper.SetInput(fillHolesFilter.GetOutput());
                       
        vtkDataSetMapper gridMapper = new vtkDataSetMapper();
        gridMapper.SetInput(gridObject);
        
        setOutlineFilter(gridObject);
        
        vtkPolyDataMapper outlineMapper = new vtkPolyDataMapper();
        outlineMapper.SetInput(getOutlineFilter().GetOutput());
        
//        vtkPolyDataMapper pointsMapper = new vtkPolyDataMapper();
//        pointsMapper.SetInputConnection(filter.GetOutputPort());
      
        gridActor = new vtkActor();
        gridActor.SetMapper(gridMapper);
        //gridActor.GetProperty().SetColor(255, 0, 0);
        
        outlineActor = new vtkActor();
        outlineActor.SetMapper(outlineMapper);
        outlineActor.GetProperty().SetColor(0.00, 0.00, 0.00);
//        
        //pointsActor = new vtkActor();
        //pointsActor.SetMapper(fillHolesMapper);
        //pointsActor.GetProperty().SetColor(0.00, 0.00, 0.00);
        
        //objectActor = testDrawingObject();
    }
    
    private vtkActor testDrawingObject()
    {
        vtkPolyDataMapper objectMapper = new vtkPolyDataMapper();
        objectMapper.SetInputConnection(triangulator.GetOutputPort());
        objectMapper.Update();
        
        vtkActor actor = new vtkActor();
        actor.SetMapper(objectMapper);
        actor.GetProperty().SetColor(0.00, 0.00, 255.00);
        
        return actor;
    }
    
    private void setFDTD3DGridDomain(double[] bounds)
    {
        extent = new int[bounds.length];        
        // object´s bounds is rounded
        // initial axes´s values is rounded down and final axes´s is rounded up
        // set initial axes´s values
        for (int i = 0; i < bounds.length; i = i + 2)
        {
            extent[i] = (int)Math.floor(bounds[i]);
        }
        // set final axes´s values
        for (int i = 1; i < bounds.length; i = i + 2)
        {
            extent[i] = (int)Math.ceil(bounds[i]);
        }        
    }
        
    private vtkPoints getCenterPointsInsideObject(vtkPoints centerPoints)
    {         
        triangulator = new vtkTriangleFilter();
        triangulator.SetInput(inputData.getPolyData());
        triangulator.Update();
        
        vtkCellLocator locator = new vtkCellLocator();
        locator.SetDataSet(triangulator.GetOutput());
        locator.BuildLocator();
        vtkPoints pointsInside = new vtkPoints();        
        
        double[] currentPoint;
        int nrOfCenterPoints = centerPoints.GetNumberOfPoints();
        listOfInsideCells = new vtkIdList();
        int cellId;
       
        for (int i = 0; i < nrOfCenterPoints; ++i)
        {
            currentPoint = centerPoints.GetPoint(i);
            cellId = locator.FindCell(currentPoint);
            
            if (cellId >= 0)       
            {
                pointsInside.InsertNextPoint(currentPoint);   
                listOfInsideCells.InsertNextId(cellId);
            }
        }
        
        System.out.println("Nr. of Voxels in Discretized Object: " + pointsInside.GetNumberOfPoints());
        
        return pointsInside;
    }
    
    private void setCellData()
    {        
       vtkPolyData polyData = triangulator.GetOutput(); 
       vtkDataArray dataArray = polyData.GetCellData().GetScalars();
       scalars = new vtkDoubleArray();
       int nrOfElements = listOfInsideCells.GetNumberOfIds();
       scalars.SetNumberOfComponents(3);
       scalars.SetNumberOfTuples(nrOfElements);
       dataArray.GetTuples(listOfInsideCells, scalars);
    }
    
    private vtkUnstructuredGrid discretizeObjectInVoxels(vtkPoints referencePoints, double resolution)
    {
        vtkUnstructuredGrid objectGrid = new vtkUnstructuredGrid();
        vtkPoints points = new vtkPoints();
        
        if (resolution == 0) resolution = 0.50;
        
        double shift = resolution / 2;
        double[] initialPoint;
        double[][] vertices;
        int globalId = 0;
        
        objectGrid.Allocate(1, referencePoints.GetNumberOfPoints());
        
        for (int i = 0; i < referencePoints.GetNumberOfPoints(); ++i)
        {
            initialPoint = referencePoints.GetPoint(i);
            initialPoint[0] -= shift;
            initialPoint[1] -= shift;
            initialPoint[2] -= shift;
            
            // calculate points´ coordenates
            vertices = generateVoxelPoints(initialPoint[0], initialPoint[1], initialPoint[2], resolution);                    
            
            // set the 8 voxel´s points              
            vtkVoxel voxel = generateVoxel(vertices, points, globalId);
            
            // insert current voxel into grid
            objectGrid.InsertNextCell(voxel.GetCellType(), voxel.GetPointIds());  
            // increment global id           
            globalId += 8;
        }
        
        objectGrid.SetPoints(points);
        objectGrid.GetCellData().SetScalars(scalars);
                
        return objectGrid;
    }
    
    private vtkVoxel generateVoxel(double[][] vertices, vtkPoints points, int globalId)
    {
        vtkVoxel currentVoxel = new vtkVoxel();
        int pointId;
        
        for (int i = 0; i < vertices.length; ++i)
        {
            pointId = globalId + i;
            points.InsertPoint(pointId, vertices[i]);  
            currentVoxel.GetPointIds().SetId(i, pointId); 
        }
                
        return currentVoxel;
    }
    
    private vtkUnstructuredGrid generateVoxels(int[] extent, double resolution)
    {
        vtkUnstructuredGrid grid = new vtkUnstructuredGrid();
        vtkPoints points = new vtkPoints();
        
        //int xLength = extent[1] - extent[0];
        int yLength = extent[3] - extent[2];
        int zLength = extent[5] - extent[4];
        
        double initialXCoord = extent[0];
        double initialYCoord = extent[2];
        double initialZCoord = extent[4];
                
        int index = 0;
        //int offset = 0;
        int globalId = 0;
        
        int i = 0;
        double xIncrementor = initialXCoord;        
                
        while (xIncrementor < extent[1])
        {
            int j = 0;
            double yIncrementor = initialYCoord;
            
            while (yIncrementor < extent[3])
            {
                int k = 0;
                double zIncrementor = initialZCoord;
                
                while (zIncrementor < extent[5])
                {
                    vtkVoxel currentVoxel = new vtkVoxel();
                    // calculate points´ coordenates
                    double[][] vertices = generateVoxelPoints(xIncrementor, yIncrementor, zIncrementor, resolution);                    
                    // set the 8 voxel´s points                    
                    for (int count = 0; count < vertices.length; ++count)
                    {
                        int pointId = globalId + count;
                        points.InsertPoint(pointId, vertices[count]);  
                        currentVoxel.GetPointIds().SetId(count, pointId); 
                    }
                    
                    // calculate unique ID for voxels                   
                    // offset = i * (yLength * zLength) + (j * yLength + k); 
                    // insert voxel into grid
                    //vtkVoxel currentVoxel = createVoxel(vertices.length, globalId);
                    //grid.Allocate(1, 1);
                    grid.InsertNextCell(currentVoxel.GetCellType(), currentVoxel.GetPointIds());
                    //System.out.println("offset: " + offset);
                    
                    zIncrementor += resolution;
                    globalId += 8;
                    ++index;
                    ++k;
                    
                    currentVoxel.Delete();
                }
                
                yIncrementor += resolution;      
                ++j;
            }            
            
            xIncrementor += resolution;
            ++i;
        }    
        
        grid.SetPoints(points);
             
        System.out.println("Number of Voxels: " + index);
        System.out.println("Number of Points set: " + globalId);
                       
        return grid;
    }
    
    private double[][] generateVoxelPoints(double xIncrementor, 
                                           double yIncrementor, 
                                           double zIncrementor,
                                           double resolution)
    {
        // set 8 points to match with 8 voxel´s points
        double[][] pointsCoords = new double[8][3];
        
        pointsCoords[0] = new double[]{xIncrementor, yIncrementor, zIncrementor};
        pointsCoords[1] = new double[]{xIncrementor + resolution, yIncrementor, zIncrementor};
        pointsCoords[2] = new double[]{xIncrementor, yIncrementor + resolution, zIncrementor};
        pointsCoords[3] = new double[]{xIncrementor + resolution, yIncrementor + resolution, zIncrementor};
        pointsCoords[4] = new double[]{xIncrementor, yIncrementor, zIncrementor + resolution};
        pointsCoords[5] = new double[]{xIncrementor + resolution, yIncrementor, zIncrementor + resolution};
        pointsCoords[6] = new double[]{xIncrementor, yIncrementor + resolution, zIncrementor + resolution};
        pointsCoords[7] = new double[]{xIncrementor + resolution, yIncrementor + resolution, zIncrementor + resolution};
        
        return pointsCoords;
    }
    
    private vtkPoints getCellCenters(double resolution)
    {
        double shift = resolution / 2;
        double initialXCoord = extent[0] + shift;
        double initialYCoord = extent[2] + shift;
        double initialZCoord = extent[4] + shift;
        
        double[] center = new double[3];
        vtkPoints points = new vtkPoints();
        int index = 0;
        
        double xIncrementor = initialXCoord;      
        double yIncrementor;
        double zIncrementor;
        
        while (xIncrementor < extent[1])
        {
            yIncrementor = initialYCoord;
            
            while (yIncrementor < extent[3])
            {
                zIncrementor = initialZCoord;
                
                while (zIncrementor < extent[5])
                {
                    center[0] = xIncrementor;
                    center[1] = yIncrementor;
                    center[2] = zIncrementor;
                    
                    points.InsertNextPoint(center);   
                                      
                    zIncrementor = zIncrementor + resolution;
                    index++;
                }
                
                yIncrementor = yIncrementor + resolution;
            }
            
            xIncrementor = xIncrementor + resolution;
        } 
        
        System.out.println("Nr. of Voxels in Computational Domain: " + index);
       
        return points;
    }
        
    /**     
     * @return the camera
     */
    public vtkCamera getCamera() 
    {
        return camera;
    }

    /**
     * @return the panel
     */
    public vtkPanel getPanel() 
    {
        return panel;
    }
    
    /**
    * VtkPipelineManager´s vtkPanel property
    * getMultRenderPanel() exposes vtkPanel to interact with Panel from GUI
    * @return the multRenderPanel
    */
    public VtkMultRenderPanel getMultRenderPanel() 
    {
        return multRenderPanel;
    }
    
    /**
     * set camera
     */
    private void setCamera() 
    {
        camera = new vtkCamera();
        camera.SetViewUp(0.00, 1.00, 0.00);
        camera.SetPosition(2, 2, 2);
        camera.SetFocalPoint(0, 0, 0);
    }

    /**
     * set Panel
     */
    private void setPanel() 
    {
        panel = new vtkPanel();
        panel.GetRenderer().SetBackground(255.00, 255.00, 255.00);
        panel.GetRenderer().AddActor(gridActor);
        //panel.GetRenderer().AddActor(objectActor);
        panel.GetRenderer().SetActiveCamera(camera);
        //panel.GetRenderer().ResetCamera();
    }
    
    private void setMultRenderPanel()
    {       
        // add actors to renderers
        // default rendering object
        multRenderPanel.GetRenderer().AddActor(gridActor);
        multRenderPanel.getRenderer2().AddActor(gridActor);
        multRenderPanel.getRenderer3().AddActor(gridActor);
        multRenderPanel.getRenderer4().AddActor(gridActor);
        
//        multRenderPanel.GetRenderer().AddActor(objectActor);
//        multRenderPanel.getRenderer2().AddActor(objectActor);
//        multRenderPanel.getRenderer3().AddActor(objectActor);
//        multRenderPanel.getRenderer4().AddActor(objectActor);
        
//        multRenderPanel.GetRenderer().AddActor(pointsActor);
//        multRenderPanel.getRenderer2().AddActor(pointsActor);
//        multRenderPanel.getRenderer3().AddActor(pointsActor);
//        multRenderPanel.getRenderer4().AddActor(pointsActor);
//        
        if (outlineFilter != null) 
        {
            multRenderPanel.GetRenderer().AddActor(outlineActor);
            multRenderPanel.getRenderer2().AddActor(outlineActor);
            multRenderPanel.getRenderer3().AddActor(outlineActor);
            multRenderPanel.getRenderer4().AddActor(outlineActor);
        }
        
        // set renderers´camera
        multRenderPanel.GetRenderer().SetActiveCamera(camera);
        multRenderPanel.getRenderer2().SetActiveCamera(camera);
        multRenderPanel.getRenderer3().SetActiveCamera(camera);
        multRenderPanel.getRenderer4().SetActiveCamera(camera);
    }
    
    private void setOutlineFilter(vtkUnstructuredGrid grid)
    {
        outlineFilter = new vtkOutlineFilter();
        outlineFilter.SetInput(grid);   
        outlineFilter.Update();
    }
    
    private vtkOutlineFilter getOutlineFilter()
    {
        return outlineFilter;
    }
    
    /**
     * load vtk libraries
     */
    private static void LoadVTKLibraries()
    {
        System.loadLibrary("vtkCommon");
        System.loadLibrary("vtkFiltering");
        System.loadLibrary("vtkGenericFiltering");
        System.loadLibrary("vtkGraphics");
        System.loadLibrary("vtkHybrid");
        System.loadLibrary("vtkImaging");
        System.loadLibrary("vtksys");
        System.loadLibrary("vtkexpat");
        System.loadLibrary("vtkfreetype");
        System.loadLibrary("vtkftgl");
        System.loadLibrary("vtkjpeg");
        System.loadLibrary("vtkzlib");
        System.loadLibrary("vtktiff");
        //System.loadLibrary("vtkMPEG2Encode");
        System.loadLibrary("vtkpng");
        System.loadLibrary("vtkDICOMParser");
        System.loadLibrary("vtkIO");
        System.loadLibrary("vtkRendering");
        System.loadLibrary("vtkVolumeRendering");
        System.loadLibrary("vtkCommonJava");
        System.loadLibrary("vtkFilteringJava");
        System.loadLibrary("vtkGenericFilteringJava");
        System.loadLibrary("vtkGraphicsJava");
        System.loadLibrary("vtkHybridJava");
        System.loadLibrary("vtkImagingJava");
        System.loadLibrary("vtkIOJava");
        System.loadLibrary("vtkGraphicsJava");
        System.loadLibrary("vtkRenderingJava");
        System.loadLibrary("vtkVolumeRenderingJava");
    }
}
